package com.onlyxiahui.framework.net.handler.data;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.onlyxiahui.framework.net.handler.data.action.DataBackAction;
import com.onlyxiahui.framework.net.handler.data.action.DataSendAction;
import com.onlyxiahui.framework.net.handler.data.action.LostAction;
import com.onlyxiahui.framework.net.handler.data.bean.HandleData;
import com.onlyxiahui.framework.net.handler.data.handler.ReceiveHandler;
import com.onlyxiahui.framework.net.handler.thread.HandlerThreadPoolBuilder;

/**
 * Description <br>
 * Date 2020-05-12 11:31:37<br>
 * 
 * @author XiaHui [onlovexiahui@qq.com]<br>
 * @since 1.0.0
 */

public class DataHandler {

	protected final Logger logger = LoggerFactory.getLogger(this.getClass());

	private final ExecutorService pool = HandlerThreadPoolBuilder.build();

	/**
	 * <br>
	 * 异常信息队列
	 */
	private final ConcurrentLinkedQueue<String> exceptionDataQueue = new ConcurrentLinkedQueue<String>();
	private final Map<String, HandleData> handleDataMap = new ConcurrentHashMap<String, HandleData>();
	private final Set<LostAction> lostActionSet = new CopyOnWriteArraySet<LostAction>();
	private final Set<ReceiveHandler> receiveHandlerSet = new CopyOnWriteArraySet<ReceiveHandler>();
	private final Set<DataSendAction> dataSendActionSet = new CopyOnWriteArraySet<DataSendAction>();

	private int handleDataMaxSize = (1 << 30) - 8;
	/**
	 * 消息超时时间，当消息发送后，这个时间内，服务器未回应，便当超时处理
	 */
	private long timeOut = 10000;

	/**
	 * 记录每次清理消息的临时变量
	 */
	private long checkTimestamp = 0;
	/**
	 * 处理消息的间隔时间
	 */
	private long checkIntervalTimestamp = 1000;

	/**
	 * 是否系统发送发生异常，如果发生异常，则将未处理的消息弹出，说明发送异常
	 */
	private LostStatus lostStatus;

	public DataHandler() {
	}

	public long getTimeOut() {
		return timeOut;
	}

	public void setTimeOut(long timeOut) {
		this.timeOut = timeOut;
	}

	public long getCheckTimestamp() {
		return checkTimestamp;
	}

	public void setCheckTimestamp(long checkTimestamp) {
		this.checkTimestamp = checkTimestamp;
	}

	public long getCheckIntervalTimestamp() {
		return checkIntervalTimestamp;
	}

	public void setCheckIntervalTimestamp(long checkIntervalTimestamp) {
		this.checkIntervalTimestamp = checkIntervalTimestamp;
	}

	public int getHandleDataMaxSize() {
		return handleDataMaxSize;
	}

	public void setHandleDataMaxSize(int handleDataMaxSize) {
		this.handleDataMaxSize = handleDataMaxSize;
	}

	public boolean isLost() {
		return null == lostStatus ? false : lostStatus.isLost();
	}

	public void setLostStatus(LostStatus lost) {
		this.lostStatus = lost;
	}

	public ConcurrentLinkedQueue<String> getExceptionDataQueue() {
		return exceptionDataQueue;
	}

	public Map<String, HandleData> getHandleDataMap() {
		return handleDataMap;
	}

	public Set<LostAction> getLostActionSet() {
		return lostActionSet;
	}

	public Set<ReceiveHandler> getReceiveHandlerSet() {
		return receiveHandlerSet;
	}

	public Set<DataSendAction> getDataSendActionSet() {
		return dataSendActionSet;
	}

	public void send(Object data) {
		this.send(null, data, null);
	}

	public void send(String key, Object data) {
		this.send(key, data, null);
	}

	public void send(String key, Object data, DataBackAction dataBackAction) {
		this.handle(new HandleData(key, data, dataBackAction));
	}

	public void receive(Object data) {
		if (null != data) {
			for (ReceiveHandler handler : receiveHandlerSet) {
				handler.receive(data, handleDataMap);
			}
		}
	}

	public void addReceiveHandler(ReceiveHandler handler) {
		receiveHandlerSet.add(handler);
	}

	public void addDataSendAction(DataSendAction action) {
		dataSendActionSet.add(action);
	}

	public void addLostAction(LostAction lostAction) {
		getLostActionSet().add(lostAction);
	}

	public void addExceptionData(String key) {
		exceptionDataQueue.add(key);
	}

	/********************************************/
	public void handle() {
		try {
			handleOutTime();
			handleException();
			handleLost();
		} catch (Exception e) {
			logger.error("", e);
		}
	}

	private void handleOutTime() {
		boolean isTime = (System.currentTimeMillis() - checkTimestamp >= checkIntervalTimestamp);
		if (isTime && !handleDataMap.isEmpty()) {
			handleOutTime(handleDataMap);
			this.checkTimestamp = System.currentTimeMillis();
		}
	}

	private void handleException() {
		String key;
		while (null != exceptionDataQueue && exceptionDataQueue.peek() != null) {
			key = exceptionDataQueue.poll();
			handleLost(key);
		}
	}

	private void handleLost() {
		if (isLost()) {
			handleLost(handleDataMap);
		}
	}

	/********************************************/

	private void handle(HandleData handleData) {
		if (null != handleData) {
			if (this.isLost()) {
				this.handleLost(handleData);
			} else {
				boolean needSave = needSave(handleData);
				if (needSave) {
					if (isHandleDataFull()) {
						this.handleLost(handleData);
						return;
					} else {
						add(handleData);
					}
				}
				Object data = handleData.getData();
				handleData.setSendTimestamp(System.currentTimeMillis());
				for (DataSendAction action : dataSendActionSet) {
					boolean mark = action.send(data);
					if (!mark) {
						this.handleLost(handleData);
					}
				}
			}
		}
	}

	public boolean isHandleDataFull() {
		return handleDataMaxSize <= handleDataMap.size();
	}

	private boolean needSave(HandleData handleData) {
		return (null != handleData && null != handleData.getKey() && !handleData.getKey().trim().isEmpty() && null != handleData.getDataBackAction());
	}

	private void add(HandleData handleData) {
		handleDataMap.put(handleData.getKey(), handleData);
	}

	/********************************************/

	private void handleOutTime(Map<String, HandleData> map) {
		List<HandleData> list = new ArrayList<HandleData>(map.values());
		List<HandleData> timeOutList = new ArrayList<HandleData>();
		map.clear();
		for (HandleData handleData : list) {
			long millisecond = System.currentTimeMillis() - handleData.getSendTimestamp();
			if (millisecond > timeOut) {
				timeOutList.add(handleData);
			} else {
				add(handleData);
			}
		}
		handleOutTime(timeOutList);
	}

	private void handleOutTime(List<HandleData> list) {
		for (HandleData handleData : list) {
			handleOutTime(handleData);
		}
	}

	private void handleOutTime(HandleData handleData) {
		if (null != handleData && null != handleData.getDataBackAction()) {
			pool.execute(new Runnable() {

				@Override
				public void run() {
					if (handleData.getDataBackAction() != null) {
						try {
							handleData.getDataBackAction().timeOut();
						} catch (Exception e) {
							logger.error("", e);
						}
					}
				}
			});
		}
	}

	/********************************************/

	private void handleLost(String key) {
		if (null != key) {
			HandleData handleData = handleDataMap.remove(key);
			if (null != handleData) {
				handleLost(handleData);
			}
		}
	}

	/********************************************/

	private void handleLost(Map<String, HandleData> map) {
		if (map.size() > 0) {
			List<HandleData> list = new ArrayList<HandleData>();
			list.addAll(map.values());
			map.clear();
			handleLost(list);
		}
	}

	private void handleLost(List<HandleData> list) {
		List<HandleData> dataList = new ArrayList<HandleData>();
		for (HandleData handleData : list) {
			if (handleData.getDataBackAction() != null) {
				handleLost(handleData);
			} else {
				dataList.add(handleData);
			}
		}
		for (LostAction a : lostActionSet) {
			a.execute(dataList);
		}
	}

	private void handleLost(HandleData handleData) {
		if (null != handleData && null != handleData.getDataBackAction()) {
			pool.execute(new Runnable() {

				@Override
				public void run() {
					if (handleData.getDataBackAction() != null) {
						try {
							handleData.getDataBackAction().lost();
						} catch (Exception e) {
							logger.error("", e);
						}
					}
				}
			});
		}
	}

	public static void main(String[] arg) {
		System.out.println((1 << 30) - 8);
	}
}
